package ws.very.util.orient
import ws.very.util.stuct.ikvdb._
import ws.very.util.orient.SQL._
trait KeyValue {

}

trait KVBaseOp extends BaseOp {
  protected def graphFac: GraphFac
  protected def className: S
  protected def fromSql = s"from $className"
  protected val db = graphFac.use(GraphConfWithoutLog())
  protected val keyF = "kvDB_K"

  protected def toKeys(it: Iterable[BaseGraph#Ele[_]]) = it.map { _.str.selectDynamic(keyF) }.toSet
  //FIXME:分离实现 清空、删表、删表重创
  def flushDB = db.noTx { _.sql(s"delete from $className").execute }
  def allKeys: Set[S] = toKeys(db.noTx { _.sql(s"select ${keyF} $fromSql").vResult })
  def keyStartWith(start: S): Set[String] = toKeys(db.noTx { _.sql(s"select $keyF $fromSql $whereKeyLike", start + "%").vResult })
  def del(key: String): Long = db.noTx { _.sql(s"delete $fromSql ${whereKeyIs} ", key).countInfluence }

  protected val whereKeyIs = s"where $keyF = ?"
  protected val whereKeyLike = s"where $keyF like ?"
  protected val whereKeyIn = s"where $keyF in ?"

  protected def create = {
    db.noTxTry { _.Classes.+(className) }
    db.noTxTry { _.sql(s"CREATE PROPERTY $className.$keyF STRING").execute }
    db.noTxTry { _.sql(s"ALTER PROPERTY $className.$keyF notnull true").execute }
    db.noTxTry { _.sql(s"ALTER PROPERTY $className.$keyF Mandatory true").execute }
    db.noTxTry { _.sql(s"CREATE INDEX $className.$keyF ON $className ($keyF) unique").execute }
    //CREATE PROPERTY user.name STRING
    //ALTER PROPERTY <class>.<property> notnull true Mandatory
    //CREATE INDEX testIndex.key ON testIndex (key) unique string
  }

}

trait KVCommon extends KVBaseOp {
  protected implicit class fixField(old: S) {
    val fix = if (old.contains(".")) old.replaceAll("""\.""", "_dot_") else old
    //    def mapField = s"$valF.$fix"

    def reback = old.replaceAll("_dot_", """\.""")
  }

  def selectKeyIs(select: S) = s"select $select $fromSql $whereKeyIs"
  def selectKeyIn(select: S) = s"select $select $fromSql $whereKeyIn"
}

object ValueFix {

  trait FixStr2Num extends ValueFix {
    override def fix(value: S) = {
      val v = super.fix(value)
      scala.util.Try(BigDecimal(v.toString())).getOrElse(v)
    }
  }
}
trait ValueFix {
  def fix(value: S): A = value
}

trait KVCollOp extends KVCommon {
  protected def valF: S
  def all(key: S) = db.noTx { _.sql(selectKeyIs(s"expand($valF)"), key).eleResult.map { _.str.value } }
  def count(key: S) = db.noTx { _.sql(selectKeyIs(s"$valF.size() as s"), key).firstEleResult.map { _.long.s } }.getOrElse(0l)
  def add(key: S, value: S*) = value.map { v =>
    db.noTx { _.sql(s"update $className add $valF= ? set $keyF = ? upsert where $keyF = ?", v, key, key).to[I] }
  }.sum
  def contains(key: S, value: S) = db.noTx { _.sql(s"select 1 from $className where $keyF = ? and ? in $valF", key, value).eleResult.nonEmpty }

}

trait KVSetOp extends SetOp with KVCollOp {
  def valF = "kvDB_set"
  override def create = {
    super.create
    db.noTxTry { _.sql(s"CREATE PROPERTY $className.$valF EmbeddedSet").execute }
  }
  def smembers(key: String): Set[String] = all(key).toSet

  def scard(key: String): Long = count(key)

  def sadd(key: String, value: String*): Long = add(key, value: _*)

  def sismember(key: String, value: String): Boolean = contains(key, value)

  def srem(key: String, value: String): Long = ???
}

trait KVSOp extends KVOp with KVMapOp {

  def valF = "kvDB_s"
  def exists(key: String): Boolean = hexists(key, valF)
  def get(key: String): Option[String] = hget(key, valF)

  def set(key: String, value: String): String = hset(key, valF, value).toString()

  def incrby(key: String, increment: Int): Long = hincrBy(key, valF, increment)

  def incr(key: String): Long = hincrBy(key, valF, 1)
}

object KVMapOp {
  trait PutAsOverride extends KVMapOp {
    override def put(key: String, values: Map[String, String]): String = {
      del(key)
      super.put(key, values)
    }
  }
}

trait KVMapOp extends KVBaseOp with MapOp with KVCommon with ValueFix {

  def hget(key: String, field: String): Option[String] =
    db.noTx {
      _.sql(selectKeyIs(s"${field.fix} as value"), key).firstEleResult.map {
        _.str.value
      }
    }.filter { x => x != null }

  def hexists(key: String, field: String): Boolean = hget(key, field).nonEmpty

  private def toMap(ele: BaseGraph#Ele[_]) = ele.props.map {
    case (k, v) =>
      k.reback -> v.toString
  } - keyF

  def hgetAll(key: String): Map[String, String] = db.noTx {
    _.sql(selectKeyIs(""), key).firstEleResult.map {
      toMap
    }.getOrElse(Map())
  }

  def hgetMany(keys: Seq[String]): Seq[Map[String, String]] = db.noTx {
    _.sql(selectKeyIn(""), keys).eleResult.map {
      toMap
    }

  }.toSeq
  def hinc(key: S, field: S, inc: BigDecimal) = db.noTx {
    _.sql(s"update $className set $keyF = :key INCREMENT ${field.fix} = :inc upsert return after $$current.${field.fix} where $keyF = :key ", Map("key" -> key, "inc" -> inc)).firstEleResult.get.bigDec.value
  }

  def hincrBy(key: String, field: String, increment: Long): Long = hinc(key, field, BigDecimal(increment)).longValue()

  private def set(key: S, vs: Map[S, S]) = {
    val nvs = vs.map { case (k, v) => k.fix -> fix(v) } + (keyF -> key)
    //    println(s"update $className set ${nvs.keySet.map { k => s"$k = :$k" }.mkString(",")} upsert where $keyF = :$keyF")
    //    println(nvs)
    db.noTx {
      _.sql(s"update $className set ${nvs.keySet.map { k => s"$k = :$k" }.mkString(",")} upsert where $keyF = :$keyF", nvs).countInfluence
    }
  }

  def hset(key: String, field: String, value: String): Long = set(key, Map(field -> value))

  def hdel(key: String, field: String): Long = ???

  def hmget(key: String, fields: Seq[String]): Seq[Option[String]] = ???

  def hmset(key: String, details: Map[String, String]): String = set(key, details).toString

  def put(key: String, values: Map[String, String]): String = hmset(key, values)

  /**
   * 注意精浮问题
   */
  def hincrByFloat(key: S, field: S, increment: D): D = hinc(key, field, BigDecimal(increment)).doubleValue()

  def hincrByDecimal(key: S, field: S, increment: BigDecimal): L = hinc(key, field, (increment)).longValue()
}


